#include "hash_secundario.h"

HashSec::HashSec(std::string& nombre, unsigned int tam_bloque, unsigned int tam_bloque_indice, unsigned int num_campo_clave) : IndiceSec(nombre, tam_bloque, tam_bloque_indice, num_campo_clave) {
	this->arch_hash = NULL;
	this->arch_oflow = NULL;
}

HashSec::~HashSec(){
	//Cierro todos los archivos que dependen del hash secundario y elimino el resto de los objetos
	this->cerrar();
	if (arch_hash != NULL)		
		delete(this->arch_hash);
	if (arch_oflow != NULL)
		delete(this->arch_oflow);
}

void HashSec::abrir() throw(ExcepAbriendoArch){
	try {
		std::string msj = "< ARCHIVO HASH SECUNDARIO >";
		(Debug::get_instance() )->mostrar_mensaje(msj);
		(Debug::get_instance() )->set_tab(true);
	
		std::string dest = this->get_nombre() + ".head";
		std::string nombre_overflow = this->get_nombre() + EXT_OFLW;
		
		Header head_hash(dest);
		dest = nombre_overflow + ".head";
		Header head_oflow(dest);
		
		head_hash.abrir();
		head_hash.hidratar(get_nombre());
		head_hash.cerrar();
		
		head_oflow.abrir();
		head_oflow.hidratar(nombre_overflow);
		head_oflow.cerrar();
		
		arch_hash = new Hash (get_nombre(),head_hash.get_tam_bloque_mae(),head_hash.get_tam_bloque_ind());
		arch_hash->cargar_registro_base( head_hash.get_formato() );
		
		arch_oflow = new ArchOverflow (nombre_overflow,head_oflow.get_tam_bloque_mae());
		arch_oflow->cargar_registro_base( head_oflow.get_formato() );
		
		//Si alguno no se puede abrir propago la excep
		arch_hash->abrir(); //arroja una excepcion en caso de no poder abrirlo
		arch_hash->agregar_observador(Buffer::get_instance());
		arch_oflow->abrir(); //arroja una excepcion en caso de no poder abrirlo
		
		(Debug::get_instance() )->set_tab(false);
		msj = "< /ARCHIVO HASH SECUNDARIO >";
		(Debug::get_instance() )->mostrar_mensaje(msj);
	} catch (ExcepSintaxisParser &e1) {
		this->cerrar();
		throw ExcepAbriendoArch ("Error - No se pudieron cargar los archivos de control. ");
	} catch (ExcepAbriendoArch &e) {
		//Si tiro excep cierro todo
		this->cerrar();
		throw e;	
	}
}
		
void HashSec::crear(std::string formato) throw(ExcepCreandoArch, ExcepSintaxisParser, ExcepBloqueTam){
	std::string msj = "< ARCHIVO HASH SECUNDARIO >";
	(Debug::get_instance() )->mostrar_mensaje(msj);
	(Debug::get_instance() )->set_tab(true);
	//Genero el formato para el archivo de hash [campoN, int]
	std::string f_hash = generar_formato_hash(formato);
	//Genero el formato para el archivo de oflow [campo0]
	std::string f_oflow = generar_formato_oflow(formato);
	//Creo los archivos internos
	this->crear_archivos_propios();
	//Creo el archivo hash
	arch_hash->crear(f_hash);
	//Creo el archivo de overflow
	arch_oflow->crear(f_oflow);
	//Si alguno tira un error propago la excep
	arch_hash->agregar_observador(Buffer::get_instance());
	(Debug::get_instance() )->set_tab(false);
	msj = "< /ARCHIVO HASH SECUNDARIO >";
	(Debug::get_instance() )->mostrar_mensaje(msj);
}

void HashSec::cerrar() {
	std::string msj = "< ARCHIVO HASH SECUNDARIO >";
	(Debug::get_instance() )->mostrar_mensaje(msj);
	(Debug::get_instance() )->set_tab(true);
	if (arch_hash != NULL)
		arch_hash->cerrar();
	if (arch_oflow != NULL)
		arch_oflow->cerrar();
	(Debug::get_instance() )->set_tab(false);
	msj = "< /ARCHIVO HASH SECUNDARIO >";
	(Debug::get_instance() )->mostrar_mensaje(msj);
}

void HashSec::ingresar(TipoStruct * reg) throw(ExcepArchCerrado,ExcepRegExistente, ExcepBloqueTam){
	std::string msj = "< ARCHIVO HASH SECUNDARIO >";
	(Debug::get_instance() )->mostrar_mensaje(msj);
	(Debug::get_instance() )->set_tab(true);
	TipoStruct* reg_hash = generar_reg_hash(reg, -1);
	TipoStruct* reg_oflow = generar_reg_oflow(reg);
	TipoStruct* reg_buscado = buscar_en_hash(reg_hash);
	int nbloque = -1;
	//Busco el reg en el hash
	if (reg_buscado != NULL){
		//Si esta, me fijo el bloque en el overflow donde insertar y agrego el nuevo reg
		TipoInt* aux = (TipoInt*) &(*reg_buscado)[1];
		nbloque = aux->get_valor();
		arch_oflow->ingresar(nbloque, reg_oflow);
		delete (reg_buscado);
	} else {
		//Si no esta, armo un reg de hash y lo ingreso, pido un bloque libre al arch de overflow
		//y meto el reg ahi.
		nbloque = arch_oflow->get_primer_bloque_libre();
		delete(reg_hash);
		reg_hash = generar_reg_hash(reg, nbloque);
		arch_hash->ingresar(reg_hash);
		arch_oflow->ingresar(nbloque, reg_oflow);	
	}
	delete(reg_hash);
	delete(reg_oflow);
	(Debug::get_instance() )->set_tab(false);
	msj = "< /ARCHIVO HASH SECUNDARIO >";
	(Debug::get_instance() )->mostrar_mensaje(msj);
}

void HashSec::quitar(TipoStruct* reg) throw(ExcepArchCerrado){
	std::string msj = "< ARCHIVO HASH SECUNDARIO >";
	(Debug::get_instance() )->mostrar_mensaje(msj);
	(Debug::get_instance() )->set_tab(true);
	TipoStruct* reg_hash = generar_reg_hash(reg, -1);
	TipoStruct* reg_oflow = generar_reg_oflow(reg);
	TipoStruct* reg_buscado = buscar_en_hash(reg_hash);

	int nbloque = -1;
	int resultado;
	//Busco el reg en el hash
	if (reg_buscado != NULL){
		//Si esta, me fijo el bloque en el overflow donde tengo que quitar
		TipoInt* aux = (TipoInt*) &(*reg_buscado)[1];
		nbloque = aux->get_valor();
		resultado = arch_oflow->quitar(nbloque, reg_oflow);
		if (resultado == SIN_ELEMENTOS)
				quitar_en_hash(reg_hash);
		else {
			if (resultado != SIN_CAMBIOS) {
				TipoStruct* reg_aux = generar_reg_hash(reg, resultado);
				arch_hash->actualizar(reg_aux);
				delete(reg_aux);
			}
		}			
		delete (reg_buscado);
	} // Sino no hago nada
	delete(reg_hash);
	delete(reg_oflow);
	(Debug::get_instance() )->set_tab(false);
	msj = "< /ARCHIVO HASH SECUNDARIO >";
	(Debug::get_instance() )->mostrar_mensaje(msj);
}

bool HashSec::consultar(std::vector<ParesDatos*> &consultas, std::vector<TipoStruct*> &resultado)
							 throw(ExcepArchCerrado,ExcepLeerBloque){
	std::string msj = "< ARCHIVO HASH SECUNDARIO >";
	(Debug::get_instance() )->mostrar_mensaje(msj);
	(Debug::get_instance() )->set_tab(true);
	bool correcta = true;
	int nbloque = -1;
	resultado.clear();
	TipoStruct* reg_hash = generar_reg_hash(consultas);
	if (reg_hash != NULL) {
		TipoStruct* reg_buscado = buscar_en_hash(reg_hash);	
		if (reg_buscado != NULL) {
			//Si esta, me fijo el bloque en el overflow desde donde consultar
			TipoInt* aux = (TipoInt*) &(*reg_buscado)[1];
			nbloque = aux->get_valor();
			arch_oflow->consultar_todos(nbloque, resultado);
			delete (reg_buscado);
		}
	} else
		correcta = false;
	delete(reg_hash);
	(Debug::get_instance() )->set_tab(false);
	msj = "< /ARCHIVO HASH SECUNDARIO >";
	(Debug::get_instance() )->mostrar_mensaje(msj);
	return correcta;					 	
}
											
TiposArchivo HashSec::get_tipo_archivo(){
	return HASH_SECUNDARIO;
}		

void HashSec::eliminar () {
	std::string msj = "< ARCHIVO HASH SECUNDARIO >";
	(Debug::get_instance() )->mostrar_mensaje(msj);
	(Debug::get_instance() )->set_tab(true);
	//Cierro los archivos
	this->cerrar();
	//Elimino ambos archivos fisicos
	arch_hash->eliminar();
	arch_oflow->eliminar();
	(Debug::get_instance() )->set_tab(false);
	msj = "< /ARCHIVO HASH SECUNDARIO >";
	(Debug::get_instance() )->mostrar_mensaje(msj);
}		

unsigned int HashSec::get_espacio_libre() throw(ExcepArchCerrado, ExcepLeerBloque){
	unsigned int resultado;
	//Asigno el espacio libre del hash utilizado
	resultado = arch_hash->get_espacio_libre() + arch_hash->get_esp_libre_indice();
	//Sumo el espacio libre del archivo de overflow
	resultado += arch_oflow->get_espacio_libre();
	return resultado;
}

unsigned int HashSec::get_cant_regs() throw(ExcepArchCerrado){
	unsigned int resultado;
	//Asigno la cant de registros utilizados en el hash
	resultado = arch_hash->get_cant_regs() + arch_hash->get_cant_regs_indice();
	//Sumo la cant de registros del arch de overflow
	resultado += arch_oflow->get_cant_regs();
	return resultado;	
}

long HashSec::get_tam() throw(ExcepArchCerrado){
	long resultado = arch_hash->get_tam() + arch_hash->get_tam_indice() + arch_oflow->get_tam();	
	return resultado;
}

std::string HashSec::generar_formato_hash(const std::string &formato){
	//ME VIENE: [TIPO CAMPO N, TIPO CAMPO 0]
	//FORMATO DE HASH: [TIPO CAMPO N, INT]
	
	std::string resultado;
	size_t pos = formato.find_first_of(',');
	resultado = formato.substr(0, pos+1);
	resultado.append("int]");
	return resultado;	
}

std::string HashSec::generar_formato_oflow(const std::string &formato){
	//ME VIENE: [TIPO CAMPO N, TIPO CAMPO 0]
	//FORMATO DE OFLOW: [TIPO CAMPO 0]
	
	std::string resultado ("[");
	size_t pos = formato.find_first_of(',');
	resultado += formato.substr(pos+1,formato.size()-1);
	
	return resultado;
}

TipoStruct* HashSec::buscar_en_hash(TipoStruct* reg_hash){
	//Creo un vector de pares de datos con la clave que se quiere consultar
	std::vector<ParesDatos*> consulta;
	std::vector<TipoStruct*> resultado;
	TipoDato* dato_clave = ((*reg_hash)[0]).clonar();
	ParesDatos * par = new ParesDatos(0, dato_clave);
	consulta.push_back( par );
	//Consulto en el archivo de hash y devuelvo la 1era (y unica) ocurrencia
	arch_hash->consultar(consulta, resultado);
	delete(par);
	if (resultado.size() == 0)
		return NULL;
	return resultado[0];
}

void HashSec::quitar_en_hash (TipoStruct* reg_hash){
	//Creo un vector de pares de datos con la clave que se quiere quitar
	std::vector<ParesDatos*> elementos;
	std::vector<TipoStruct*> res_quitar;
	TipoDato* dato_clave = ((*reg_hash)[0]).clonar();
	ParesDatos * par = new ParesDatos(0, dato_clave);
	elementos.push_back( par );
	//Quito la entrada en el archivo de hash
	arch_hash->quitar(elementos, res_quitar);
	delete(par);
	std::vector<TipoStruct*>::iterator it;
	for (it = res_quitar.begin(); it != res_quitar.end(); ++it)
		delete (*it);
}

TipoStruct* HashSec::generar_reg_hash(std::vector<ParesDatos*> &consultas){
	TipoStruct* reg = NULL;
	std::vector<ParesDatos*>::iterator it = consultas.begin();
	//Recorro las consultas. Si una es por campo clave del secundario
	//creo un reg base de hash y lo lleno con el dato q se esta consultando
	while ((it != consultas.end()) && (reg == NULL)) {
		ParesDatos* par = (*it);	
		if (par->get_numero_campo() == this->num_campo_clave) {
			reg = (TipoStruct*)arch_hash->get_registro_base()->clonar();
			(*reg)[0] = *(par->get_valor());
		}
		it++;
	}
	return reg;
}

TipoStruct* HashSec::generar_reg_hash(TipoStruct* reg, int num_bloque){
	//Creo un nuevo registro del tipo base de hash y lo lleno con los datos
	TipoStruct* reg_retorno = (TipoStruct*)arch_hash->get_registro_base()->clonar();
	(*reg_retorno)[0] = (*reg)[this->num_campo_clave];
	TipoInt aux (num_bloque);
	(*reg_retorno)[1] = aux;
	return reg_retorno;
}

TipoStruct* HashSec::generar_reg_oflow(TipoStruct* reg){
	//Creo un nuevo registro del tipo base de oflow y lo lleno con los datos
	TipoStruct* reg_retorno = (TipoStruct*)arch_oflow->get_registro_base()->clonar();
	(*reg_retorno)[0] = (*reg)[0];
	return reg_retorno;	
}

void HashSec::crear_archivos_propios(){
	//Creo un archivo hash, con el tamaño y el nombre pasados como param
	this->arch_hash = new Hash(this->get_nombre(), this->get_tam_bloque(), this->get_tam_indice());
	//Concateno el nombre con la extension de overflow y creo el archivo de overflow tmb con el mismo tam de bloque
	std::string nombre_overflow = this->get_nombre() + EXT_OFLW;
	this->arch_oflow = new ArchOverflow(nombre_overflow, tam_bloque);
}
